#include "int80.h"
#include "in_out.h"
#include "multitasker.h"
#include "file_desc.h"
#include "../driver/ata.h"
#include "../../std/errno.h"

void yeld();

#define FD_CANT 50

#define O_CREATE 1
#define O_EXCL 2

buffer_t buffers[FD_CANT];

file_ram_t files[FD_CANT];

file_desc_t files_desc[FD_CANT];


int next_sector(file_ram_t* file, int extend){

	int flag=0;	
	
	writeDisk(ATA0, &file->fileData, sizeof(FileData), file->data_sector, 0);
		
	if(file->data_sector >= file->refDataSector.list[file->ref_data].sector +
									file->refDataSector.list[file->ref_data].sectorCant-1){
			
			if(!sectorPresent(file->refDataSector.list[file->ref_data].sector +
										file->refDataSector.list[file->ref_data].sectorCant)){
				setSector(file->refDataSector.list[file->ref_data].sector +
										file->refDataSector.list[file->ref_data].sectorCant);
				flag=1;
			
			}

			if(file->ref_data+1>=file->refDataSector.cant){
				if(file->refDataSector.cant>=REF_DATA_CANT){
				
					file->ref_data_sector=file->refDataSector.next;
					readDisk(ATA0, &file->refDataSector,sizeof(RefDataSector), file->ref_data_sector, 0);
					file->ref_data=0;
				}
				else{
					flag=1;
					if(!extend){
						return 0;
					}
					file->refDataSector.cant++;
					if(file->refDataSector.cant==REF_DATA_CANT){
						int new_refData_sector=getFileFreeSector();
						file->refDataSector.next=new_refData_sector;
						writeDisk(ATA0,&file->refDataSector,sizeof(RefDataSector),
												file->ref_data_sector,0);
						file->ref_data_sector=new_refData_sector;
						file->refDataSector.cant=1;
					}
					int new_dirData_sector=getFileFreeSector();	

					file->refDataSector.list[file->refDataSector.cant-1].sector = new_dirData_sector;
					file->refDataSector.list[file->refDataSector.cant-1].sectorCant = 1;
					writeDisk(ATA0, &file->refDataSector, sizeof(RefDataSector), file->ref_data_sector,0);
					file->ref_data++;
				}
				
			}
			else{
				file->ref_data++;
			}
			
			file->data_sector=file->refDataSector.list[file->ref_data].sector;
			
	}
	else{
		file->data_sector++;	
	}
	
	if(!flag){
		readDisk(ATA0, &file->fileData, sizeof(FileData), file->data_sector, 0);
	}
	else{
		int i;
		for(i=0;i<SECTOR_SIZE-1;i++){
			file->fileData.data[i]='\0';	
		}
	}
	
	return 1;
}

int file_write(int cursor, char* buffer,int cant){
	int i;
	/*
	Selecciona el buffer.
	*/
	file_ram_t * file=&files[cursor];
	
	if(!file->present){
		return -1;
	}
	
	/*
	Lee los caracteres del buffer.
	*/
	for(i=0;i<cant;i++){
	
		/*
		Si se vacio.
		*/
		if(file->offset>=SECTOR_SIZE){
			
			next_sector(file,1);
			file->offset=0;
			
		}
		
		/*
		Leo un caracter del buffer, y lo pongo en el buffer que me pasaron.
		*/
		file->fileData.data[file->offset]=*(buffer+i);
		file->offset++;
	}
	
	writeDisk(ATA0, &file->fileData, sizeof(FileData), file->data_sector, 0);
	
	return i;

}


int file_read(int cursor, char* buffer,int cant){
	int i;
	
	/*
	Selecciona el buffer.
	*/
	file_ram_t * file=&files[cursor];
	
	if(!file->present){
		return -1;
	}
	
	/*
	Lee los caracteres del buffer.
	*/
	for(i=0;i<cant;i++){
	
		/*
		Si se vacio.
		*/
		if(file->offset>=SECTOR_SIZE){
			
			if(!next_sector(file,0)){
				return 0;
			}
			file->offset=0;
		}
		
		/*
		Leo un caracter del buffer, y lo pongo en el buffer que me pasaron.
		*/
		*(buffer+i)=file->fileData.data[file->offset];
		file->offset++;
	}
	
	
	return i;
}

/*
Handler del systemcall read. Lee del buffer indicado, y si se vacia antes de leer lo que se le pidio,
se bloquea esperando mas informacion.
*/
int buffer_read(int cursor, char* buffer,int cant){
	int i;
	
	/*
	Selecciona el buffer.
	*/
	buffer_t * buff=&buffers[cursor];
	
	if(!buff->present){
		return -1;
	}
	/*
	Lee los caracteres del buffer.
	*/
	for(i=0;i<cant;i++){
	
		/*
		Si se vacio.
		*/
		while(buff->start==buff->end){
			
			/*
			Me bloqueo, me agrego a la cola de espera del buffer, y cedo el procesador.
			*/
			block(Blocked_read);
			add_actual_task_to_block_list(&buff->block_list);
			yeld();
			
			/*
			Si se desbloqueo por una señal, retorno -1 del lado del usuario.
			*/
			if(buff->start==buff->end){
				return -1;
				
			}
		}
		
		/*
		Leo un caracter del buffer, y lo pongo en el buffer que me pasaron.
		*/
		*(buffer+i)=buff->array[buff->start];
		buff->start=(buff->start+1)%BUFFER_SIZE;
	}
	
	/*
	Desbloqueo a todos los que intentaban leer de esta cola.
	*/
	unblock_all_from_when(&buff->block_list,Blocked_write);
	
	
	return i;
}


/*
Handler del systemcall write. Escrive en el buffer indicado, y si se llena antes de leer lo que se le pidio,
se bloquea esperando un espacio.
*/
int buffer_write(int cursor, char* buffer,int cant){
	int i;
	int tmp;
	
	/*
	Selecciona el buffer.
	*/
	buffer_t * buff=&buffers[cursor];
	
	if(!buff->present){
		return -1;
	}
	/*
	Escrive los caracteres en el buffer.
	*/
	tmp=(buff->end+1)%BUFFER_SIZE;
	for(i=0;i<cant;i++,tmp=(buff->end+1)%BUFFER_SIZE){
		
		/*
		Si se lleno
		*/
		while(tmp==buff->start){
		
			/*
			Me bloqueo, me agrego a la cola de espera del buffer, y cedo el procesador.
			*/
			block(Blocked_write);
			add_actual_task_to_block_list(&buff->block_list);
			yeld();
			
			/*
			Si se desbloqueo por una señal, retorno -1 del lado del usuario.
			*/
			if(tmp==buff->start){
				return -1;
			}
			tmp=(buff->end+1)%BUFFER_SIZE;
		}
		
		/*
		Leo un caracter del buffer que me pasaron y lo escrivo en el buffer seleccionado.
		*/
		buff->array[buff->end]=*(buffer+i);
		buff->end=tmp;
	}
	
	/*
	Desbloqueo a todos los que estan intentando leer de este buffer.
	*/
	unblock_all_from_when(&buff->block_list,Blocked_read);
	
	
	return i;
}

/*
Agrega un buffer a la lista de buffers.
*/
void get_buffer(int n, buffer_t ** buff){
	if(files_desc[n].type==FIFO_TYPE){
		*buff=&buffers[files_desc[n].cursor];
	}
}

int get_free_buffer(){
	int n;
	for(n=0;buffers[n].present;n++);
	buffers[n].present=1;
	buffers[n].start=0;
	buffers[n].end=0;
	buffers[n].block_list=-1;
	return n;
}

void goto_file_start(int n,int header){
	File_Disk file_disk;
	readDisk(ATA0, &file_disk, sizeof(File_Disk), header, 0);
	
	files[n].ref_data_sector=file_disk.sectorRefData;
	
	readDisk(ATA0, &files[n].refDataSector, sizeof(RefDataSector), files[n].ref_data_sector, 0);
	
	files[n].ref_data=0;
	files[n].data_sector=files[n].refDataSector.list[0].sector;
	files[n].offset=0;
	
	readDisk(ATA0, &files[n].fileData, sizeof(FileData), files[n].data_sector, 0);
}

int get_free_file(int header){

	int n;
	for(n=0;files[n].present;n++);

	files[n].present=1;
	
	goto_file_start(n,header);
	
	files[n].block_list=-1;

	return n;
}

int get_used_in_out(int header){
	
	int n;
	for(n=0 ; n<FD_CANT ; n++){
		if(files_desc[n].header_sector==header){
			return n;
		}
	}
	
	return -1;
}

int increment_count(int n){
	files_desc[n].count++;
}

int get_free_in_out(int header,unsigned int type,int mode){
	int n;
	for(n=0;files_desc[n].present;n++);
	files_desc[n].present=1;
	files_desc[n].header_sector=header;
	files_desc[n].count=1;
	files_desc[n].type=type;
	files_desc[n].mode=mode;
	
	switch(type){
		case DIRECTORY_TYPE:
		case FILE_TYPE:
			files_desc[n].cursor=get_free_file(header);
			break;
		case FIFO_TYPE:
			if(files_desc[n].cursor!=-1){
				files_desc[n].cursor=get_free_buffer(header);
			}
			break;
		default:
			files_desc[n].cursor=-1;
	}
	
	return n;
}

//void delete_in_out(int n){
//	buffers[n].present=0;
//}

uint32_t sys_read(registers_t* regs){

	int file_number=get_fd(regs->ebx);
	
	if(file_number==-1){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	file_desc_t* file=&files_desc[file_number];
	
	if(!file->present){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	if(!(file->mode & READ_PERMISSION)){
		errno=ERR_PERM;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	int (*read)(int cursor, char* buffer,int cant);
	
	switch(file->type){
		case DIRECTORY_TYPE:
		case FILE_TYPE:
			read=file_read;
			break;
		case FIFO_TYPE:
			read=buffer_read;
			break;
		default:
			errno=WRONG_TYPE;
			regs->eax=-1;
			return 0;
	
	}
	
	regs->eax=read(file->cursor,(char*)regs->ecx,regs->edx);
	
	return 0;
	
}


uint32_t sys_write(registers_t* regs){
	
	int file_number=get_fd(regs->ebx);
	
	if(file_number==-1){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	file_desc_t* file=&files_desc[file_number];
	
	if(!file->present){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	if(!(file->mode & WRITE_PERMISSION)){
		errno=ERR_PERM;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	int (*write)(int cursor, char* buffer,int cant);
	
	switch(file->type){
		case DIRECTORY_TYPE:
		case FILE_TYPE:
			write=file_write;
			break;
		case FIFO_TYPE:
			write=buffer_write;
			break;
		default:
			errno=WRONG_TYPE;
			regs->eax=-1;
			return 0;
	
	}
	
	regs->eax=write(file->cursor,(char*)regs->ecx,regs->edx);
	
	return 0;
	
}


uint32_t sys_lseek(registers_t* regs){
	int file_number=get_fd(regs->ebx);
	
	if(file_number==-1){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	file_desc_t* file=&files_desc[file_number];
	
	if(!file->present){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}

	switch(file->type){
		case DIRECTORY_TYPE:
		case FILE_TYPE:
			break;
		case FIFO_TYPE:
		default:
			errno=WRONG_TYPE;
			regs->eax=-1;
			return 0;
	}
	
	goto_file_start(file->cursor,file->header_sector);
	
	while(regs->ecx>=511){
		next_sector(&files[file->cursor], 1);
		regs->ecx-=511;
	}
	
	files[file->cursor].offset=regs->ecx;
	
	regs->eax=0;
	return 0;
}


uint32_t sys_open(registers_t* regs){
	
	File * f=getFileByPath(getCurrentDir(), (char*)regs->ebx);

	int mode=0;


	if(regs->ecx & O_CREATE){
		if((regs->ecx & O_EXCL) && f!=NULL){
			errno=ERR_FILEX;
			regs->eax=-1;
			return 0;
		}
		
		if(f==NULL){
			f=create_empty_file((char*)regs->ebx,777);
			if(f==NULL){
				errno=UNKNOW;
				regs->eax=-1;
				return 0;
			}
		}
	}
	else{
		if(f==NULL){
			errno=ERR_UNEX;
			regs->eax = -1;
			return 0;
		}
	}
	
	
	if(regs->ecx & O_RD){
		if(!hasPermission(f, READ_PERMISSION)){
			errno=ERR_PERM;
			regs->eax=-1;
			return 0;
		};
		mode=mode | READ_PERMISSION;
		if(regs->ecx & O_WR){
			if(!hasPermission(f, WRITE_PERMISSION)){
				errno=ERR_PERM;
				regs->eax=-1;
				return 0;
			}
			mode=mode | WRITE_PERMISSION;
		}
	}
	else if(regs->ecx & O_WR){
		if(!hasPermission(f, WRITE_PERMISSION)){
			errno=ERR_PERM;
			regs->eax=-1;
			return 0;
		}
		mode=mode | WRITE_PERMISSION;
	}
	else{
		if(!hasPermission(f,READ_PERMISSION) || !hasPermission(f,WRITE_PERMISSION)){
			errno=ERR_PERM;
			regs->eax=-1;
			return 0;
		}
		mode=mode | WRITE_PERMISSION;
		mode=mode | READ_PERMISSION;
	}
	
	
	int fd=-1;
	
	if(f->type==FIFO_TYPE){
		fd=get_used_in_out(f->sector);
	}
	
	if(fd==-1){
		fd=get_free_in_out(f->sector,f->type, mode);
	}
	else{
		files_desc[fd].count++;
	}
	
	regs->eax = set_fd(fd);
	
	return 0;
}

void free(file_desc_t* file){

	switch(file->type){
		case DIRECTORY_TYPE:
		case FILE_TYPE:
			files[file->cursor].present=0;
			break;
		case FIFO_TYPE:
			 buffers[file->cursor].present=0;
			break;
	}
	file->present=0;
	
}

uint32_t sys_close(registers_t* regs){

	int file_number=get_fd(regs->ebx);
	
	if(file_number==-1){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	file_desc_t* file=&files_desc[file_number];
	
	if(!file->present){
		errno=WRONG_FD;
		regs->eax=-1;
		
		/*
		Retorno cero porque no quiero cambiar el stack.
		*/
		return 0;
	}
	
	set_fd2(regs->ebx,-1);
	file->count--;
	
	if(file->count==0){
		free(file);
	}
	
	return 0;
}

/*
Registra los manejadores de systemcalls.
*/
init_in_out(){
	register_functionality(3,sys_read);
	register_functionality(4,sys_write);
	register_functionality(5,sys_open);
	register_functionality(6,sys_close);
	register_functionality(19,sys_lseek);
	int i;
	for(i=0;i<FD_CANT;i++){
		buffers[i].present=0;
	}
	
}
